<?php
/*
Clase común para los parsers de pages y userControls
*/
abstract class ContainerParser extends CommonParser
{
	protected $class_name = "";
	protected $i_file;
	protected $declarations;
	protected $referenced_parsers;
	protected $code_file = "";
	protected $code = "";
	protected $code_file_insert_ix;
	protected $include_insert_ix;
				
	protected abstract function getAllowedExtensions();
			
	function __construct($filename)
 	{	
 		//global $debug_level;
 				
		$ext = strtolower(get_extension($filename));
		$extensions = $this->getAllowedExtensions();
 		if(!in_array($ext, $extensions))		
 		{
 			throw new Exception("El archivo debe tener extensión ".implode(", ",$extensions));
 		}
 		$this->i_file = $filename;		
		
		$this->content = file_get_contents($this->i_file);
		$this->inner_content = $this->content;
		if($debug_level>1) echo "Content mide ".strlen($this->content)."\n".substr($this->content,0,25)."...\n";

//PARSE DECLARATIONS!!
		$this->parseDeclarations();
		
		//Podría haber sido establecido por las declaraciones
		if(!$this->class_name)
		{
			$this->class_name = strip_extension(basename($this->i_file));
			if(strpos($this->class_name, ".")!==false) {
				$this->err("El nombre de archivo no puede contener puntos, salvo el de la extensión");
			}
		}
		
		if($this->code_file!="")		
			$candidate = $this->code_file;
		else
			$candidate = $this->i_file.".php";
			
		if($debug_level>0) echo "candidate: $candidate\n";
		if(file_exists($candidate))
		{
			/* ATENCIOOOOON!!! 
			Aquí me falta sacar el contenido del codefile, y quitarle el principio y el final
			O sea, quitarle la declaración de clase y el cierre de clase, porque va a ir contenido en otro lado.
			....
			Aunque, por otro lado, podría tomarlo como base y luego insertar la parte generada dentro!
			En este caso, lo que tengo que hacer es localizar el punto de inserción dentro del codefile
			*/
			if($debug_level>2) echo "Encontrado codefile '$candidate'\n";
			$code = file_get_contents($candidate);
			$ix = strpos($code, "class ".$this->class_name);
			$this->include_insert_ix = $ix;
			$ix = strpos($code, "{", $ix);
			if($ix)
			{				
				$this->code_file = $candidate;
				$this->code = $code;
				$this->code_file_insert_ix = $ix+1;
				if($debug_level>3) echo "Codefile OK. pos. inserción: ".$this->code_file_insert_ix."\n";
			}			
		}
		else
		{
			throw new Exception("Containers sin codefile no implementadas.");
		}

		if($debug_level>1) echo "CommonParser:construct() OK.   content mide ".strlen($this->content)."\n";
 	}
	
	public function parse()
	{
		try
		{
			parent::parse();
			//ya tengo subparsers de los simples y tags
		}
		catch (Exception $ex)
		{
			echo "Excepción al procesar ".$this->i_file.".\n".$ex->getMessage()."\n";
			exit();
		}
				
		//Hago que en mis subparsers estén todos		
		$this->subparsers = $this->getAllSubparsers();
	}
	
	//devuelve todos los sub-compilables (archivos fuente de controles de usuario)
	public function getRegisteredControlSources()
	{
		$ret = array();
		if(sizeof($this->declarations["register"])>0)
		{
			foreach($this->declarations["register"] as $reg)
			{
				$ret[] = $reg["src"];
			}
		}
		return $ret;
	}
	
	//esto se las tiene que apañar para localizar el NOMBRE DE CLASE (no el ascx)
	public function getRegisteredControlClassName($tagPrefix, $tagName)
	{
		if(array_key_exists($tagPrefix, $this->referenced_parsers))
		{
			if(array_key_exists($tagName, $this->referenced_parsers[$tagPrefix]))
			{
				return $this->referenced_parsers[$tagPrefix][$tagName]->getClassName();
			}
		}
		throw new Exception("No se ha registrado ningún componente válido $tagPrefix:$tagName");
	}
	
	public function buildSource()
	{
		global $debug_level;
		if($debug_level>3) echo $this->i_file."->buildSource()\n";
		
		$cw = new CodeWriter();
		//igual puedo reciclar la declaración del codefile
		/*
		$cw->line("<?php");
		$cw->line();
		$cw->line("class ".$this->class_name." extends Page {");
		*/
		$cw->line();
		$cw->line("/* AUTOGENERATED MEMBERS */");
		foreach($this->subparsers as $id=>$subp)
		{
			$cw->line("protected \$".$id.";");
		}
		$cw->line();
		
		
		$cw->line("/* CONSTRUCTOR */");
		$cw->line("function __construct(\$obj=null, \$container=null) {");
		$cw->line("parent::__construct(\$obj, \$container);");
		$cw->line("/* Inicializar miembros */");
		foreach($this->subparsers as $id=>$subp)
		{
			//según el tipo de trasto que sea, se llamará al constructor con distintas cosas			
			//$cw->line("\$".$id." = new ".$subp->getRuntimeClassName()."(););			
			$cw->insert($subp->getInitCode($this));
		}				
		$cw->line("}");
		$cw->line();
		$cw->line();
		
		
		$cw->line("public function Render() {");
		//Como los pages Y CONTROLS no tienen innerHTML, aquí no puede llegar a sobreescribirse el marcado original
		//para cada tag raíz, sustituyo su texto literal por una llamada a su render()
		$output = $this->content;
		foreach($this->root_ids as $root_id)
		{
			echo "Render de $root_id\n";
			if(false)
			//if($this->tags[$root_id]["isUc"])
			{
				echo "\tEs user control\n";				
				$tmp = explode(":", $this->tags[$root_id]["tag"]);				
				$find_str = $this->referenced_parsers[$tmp[0]][$tmp[1]]->getOriginalContent();
				echo "\tSu contenido es: ".substr($find_str,0,50)."..\n";
			}
			else
			{
				$find_str = $this->subparsers[$root_id]->getOriginalContent();
				echo "\tSu contenido es: ".substr($find_str,0,50)."..\n";
			}
			$rep_str = "<?=\$this->".$root_id."->Render()?>";
			$output = str_replace($find_str, $rep_str, $output);
		}
		
		$cw->line("ob_start();");
		$cw->line("?>");
		$cw->insert($output);
		$cw->line("<?php");
		$cw->line("return ob_get_clean();");
		$cw->line("}");
		$cw->line();
		
		
		$cw->line("/* CODEBEHIND ORIGINAL */");
		
		/*
		$cw->line("}");
		$cw->line("?>");
		*/	
		
		//INSERTAR EN EL CODEBEHIND ORIGINAL
		//salvar lo generado hasta ahora
		$main = $cw->toString();
		
		$part1 = substr($this->code, 0, $this->include_insert_ix);
		$part2 = substr($this->code, $this->include_insert_ix, $this->code_file_insert_ix-$this->include_insert_ix);
		$part3 = substr($this->code, $this->code_file_insert_ix);
				
		/*
		INCLUDES - Para las del framework, asumo que están en el include_path
		*/
		$cw = new CodeWriter();
		$cw->line("require_once(\"util.php\");");
		$cw->line("require_once(\"control.php\");");
		$cw->line("require_once(\"htmlControl.php\");");
		$cw->line("require_once(\"inputControl.php\");");
		$cw->line("require_once(\"container.php\");");
		$cw->line("require_once(\"userControl.php\");");
		$cw->line("require_once(\"page.php\");");
		$sources = $this->getRegisteredControlSources();
		if(sizeof($sources)>0)
		{
			foreach($sources as $src)
			{
				$cw->line("require_once(\"".get_bin_name($src)."\");");
			}
		}
		$includes = $cw->toString();
		
		return $part1.$includes."\n\n".$part2.$main.$part3;		
	}
	
	//analiza las declaraciones <!PLEA ..> y construye un array con la información procesada
	//modifica el content para eliminar esas filas
	//por esto último, debe llamarse al principio del constructor, pero después de haber obtenido el content
	protected function parseDeclarations()
	{
		//global $debug_level;
		
		$rex = "/\\<\\!PLEA(.*?)\\>/s";
		preg_match_all($rex, $this->content, $matches);
		{
			
			if($debug_level>0) print_r($matches);
			foreach($matches[1] as $match)
			{
				$rex2 = "/\\b\\w+?\\b/";
				preg_match($rex2, $match, $firstWord);
				switch(strtolower($firstWord[0]))
				{
					case "register":
						$this->parseRegisterDeclaration($match);
						break;
					case "import":						
						$this->parseImportDeclaration($match);
						break;
					case "outputcache":						
						$this->parseOutputCacheDeclaration($match);
						break;
					case "master":						
						$this->parseMasterDeclaration($match);
						break;
					default:
						$this->parseContainerDeclaration($match);
						break;
				}
			}
			$this->content = str_replace($matches[0], "", $this->content);
		}
		
		//listo, ahora invocaré a otros parsers si es necesario por los register
		if($debug_level>4) echo "Recorriendo declarations[register]\n";
		if($debug_level>4) print_r($this->declarations["register"]);
		if(sizeof($this->declarations["register"])>0)
		{
			foreach($this->declarations["register"] as $ref)
			{
				$ref_p = new UserControlParser($ref["src"]);
				$ref_p->parse();
				$this->referenced_parsers[$ref["tagPrefix"]][$ref["tagName"]] = $ref_p;
			}
		}
	}
	
	//esta es la que procesará las directivas "page", "control", y lo que pueda haber
	protected abstract function parseContainerDeclaration($str);
	
	protected function parseRegisterDeclaration($str)
	{
		//global $debug_level;
				
		if($debug_level>0) echo "str: $str\n";
		$cnt = preg_match("/\\bSrc=\"([\\w\\.]+?)\"/", $str, $match);
		if($debug_level>1) echo "cnt: $cnt\n";
		
		if($cnt!=1) throw new Exception("La declaracion Register no tiene el atributo requerido 'Src'");
		else
			$src = $match[1]; 
			
		$cnt = preg_match("/\\bTagPrefix=\"(\\w+?)\"/i", $str, $match);
		if($cnt!=1) throw new Exception("La declaracion Register no tiene el atributo requerido 'TagPrefix'");
		else
			$tagPrefix = $match[1];
			
		$cnt = preg_match("/\\bTagName=\"(\\w+?)\"/i", $str, $match);
		if($cnt!=1) throw new Exception("La declaracion Register no tiene el atributo requerido 'TagName'");
		else
			$tagName = $match[1];
			
		$this->declarations["register"][] = array("src"=>$src, "tagPrefix"=>$tagPrefix, "tagName"=>$tagName);
	}
	
	protected function parseImportDeclaration($str)
	{
		throw new Exception("Import declaration not implemented.");
	}
	protected function parseOutputCacheDeclaration($str)
	{
		throw new Exception("OutputCache declaration not implemented.");
	}
	protected function parseMasterDeclaration($str)
	{
		throw new Exception("Master declaration not implemented.");
	}
	
	
	public function getClassName()
	{
		return $this->class_name;
	}
	
}




?>